//
//  Websocket.swift
//  Fiveo
//
//  Created by 张朋朋 on 12/20/24.
//

import Starscream
import UIKit
import RouterService
import CommonBasicModule
import ConstantModule
import UMengSDK
import Log

enum WebSocketReceiveType: String {
    case SetSessionId
    case SetAgoraAppid
    case SetAgoraToken
    case SetAgoraChannelId
    case None
    
    
    case CreateSessionResult
    case AgoraChannelInfo
    case AgoraToken
}

protocol FiveoWebSocketDelegate: AnyObject {
    func webSocket(event: WebSocketReceiveType, params: [String: Any]?)
    func webSocket(speakstatus: FiveoSpeakStatus , params: [String: Any]?)
    func webSocket(error: Error?)
    func webSocket(agoraComplation: AgoraModel, audioOnly: Bool)
    func webSocket(allEvent: WebSocketEvent)
    
}

struct AgoraModel {
    var agora_token: String = ""
    var agora_channel_id: String = ""
    var agora_appid: String = ""
    var client_uid: String = ""
    
    var agora_token_flg: Int = 0
    var agora_channel_id_flg: Int = 0
    var agora_appid_flg: Int = 0
    var agora_section_id_flg: Int = 0
    var isfinashConfig: Bool {
        return agora_token_flg == agora_appid_flg &&
        agora_appid_flg == agora_channel_id_flg && agora_section_id_flg == agora_channel_id_flg  &&
        agora_channel_id_flg != 0
    }
}
class WebSocketParams: Codable {
    static var defult = WebSocketParams()
    var modelid: String = App.URLConstant.modelName
    var voice_type: String = ""
    var vad_neg_threshold: String = "0.5"
    var vad_pos_threshold: String = "0.98"
    var vad_min_speech_duration_ms: String = "400"
    var vad_min_silence_duration_ms: String = "800"
    var system_prompt_custom_subttile: String = ""
}
class FiveoWebSocket {
    
    static let share = FiveoWebSocket()
    weak var delegate: FiveoWebSocketDelegate?
    var isOnlyAudio = false
    private var request_id: String = ""
    private(set) var sessionId = ""
    private var socket: WebSocket?
     var isConnected: Bool = false
    
    var voiceType: TTSType {
        let type =  TTSType.ttsype(ttsype: params.voice_type)
        MHLog.log(type.voiceType)
        return type
    }
    
    private var connnectCompletion:((_ success: Bool) -> Void)?
    private var disconnnectCompletion:((_ success: Bool) -> Void)?
    private(set) lazy var rtc: AgoraModel = {
        let v = AgoraModel()
        return v
    }()
    lazy var params: WebSocketParams = {
        let v = WebSocketParams()
        return v
    }()
    private lazy var engine: NativeEngineFiveo = {
        let v = NativeEngineFiveo()
        return v
    }()
    
    func reconnnet(params: WebSocketParams, hello: Bool = false){
        self.params = params
        // 构建 URL
        disconnect { [weak self] _ in
            guard let self = self else { return }
            let token = JwtTokenUtil.generateToken()
            var urlComponents = URLComponents(string: App.URLConstant.socketWs)!
            urlComponents.queryItems = [
                URLQueryItem(name: "audio_only", value: String(self.isOnlyAudio)),
                URLQueryItem(name: "voice_type", value: String(params.voice_type)),
                URLQueryItem(name: "jwt", value: token),
                URLQueryItem(name: "vad_neg_threshold", value: String(params.vad_neg_threshold)),
                URLQueryItem(name: "vad_pos_threshold", value: String(params.vad_pos_threshold)),
                URLQueryItem(name: "vad_min_speech_duration_ms", value: String(params.vad_min_speech_duration_ms)),
                URLQueryItem(name: "vad_min_silence_duration_ms", value: String(params.vad_min_silence_duration_ms)),
            ]
            if hello {
                urlComponents.queryItems?.append(URLQueryItem(name: "init_prompt", value: "你好"))
            }
            if !params.modelid.isEmpty {
                urlComponents.queryItems?.append(URLQueryItem(name: "model_name", value: String(params.modelid)))
            }
            guard let url = urlComponents.url else {
                MHLog.log("Invalid WebSocket URL")
                return
            }
            MHLog.log("WebSocket URL: \(url)")
            var request = URLRequest(url: url)
            request.timeoutInterval = 5
            request.setValue("Bearer \(RouterService.rs.userInfo.refresh)", forHTTPHeaderField: "Authorization")
            self.socket = WebSocket(request: request, engine: engine)
            self.socket?.delegate = self
            self.socket?.connect()
        }
        
    }
    // 关闭scoket
    func disconnect(disconnnectCompletion:((Bool) -> Void)?) {
        if isConnected == false {
            disconnnectCompletion?(true)
            return
        }
        self.disconnnectCompletion = disconnnectCompletion
        socket?.disconnect()
    }
    // 开始链接
    func startConnect(params: WebSocketParams, connnectCompletion: ((Bool) -> Void)?) {
        self.connnectCompletion = connnectCompletion
        reconnnet(params: params, hello: false)
    }
}
extension FiveoWebSocket:  WebSocketDelegate {
    
    func didReceive(event: WebSocketEvent, client: any Starscream.WebSocketClient) {
        delegate?.webSocket(allEvent: event)
        MHLog.log(event)
        switch event {
        case .connected(let headers):
            isConnected = true
            CreateSession()
            MHLog.log("websocket is connected: \(headers)")
        case .disconnected(let reason, let code):
            isConnected = false
            connnectCompletion?(false)
            self.disconnnectCompletion?(true)
            self.disconnnectCompletion = nil
            MHLog.log("websocket is disconnected: \(reason) with code: \(code)")
        case .text(let string):
            if let data = string.data(using: .utf8),
               var json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
                var type = json["type"] as? String ?? ""
                if let reciveType = WebSocketReceiveType(rawValue: type){
                    headleReceviceTextType(reciveType: reciveType, dic: json)
                    return
                }
                // 截取最后的type
                json = removefirstType(jsonString: string) ?? [:]
                type = json["type"] as? String ?? ""
                if let fiveoStatus = FiveoSpeakStatus(rawValue: type){
                    headleFiveoStatus(reciveType: fiveoStatus, dic: json)
                }
                return
            }
            MHLog.log("Received text: \(string)")
        case .binary(let data):
            MHLog.log("Received data: \(data.count)")
        case let .error(error):
            // 57 主动挂断、 1009 没有网
            isConnected = false
            connnectCompletion?(false) // socket网络断开，
            if let error = error as? NSError,
                error.code != 1005 &&
                error.code != 57 {
                NotificationCenter.default.post(name: Notification.Name("socketClose"), object: nil)
            }
            if let error = error as? NSError {
                let codedec = codeMessage(code: error.code)
                if !codedec.isEmpty {
                    UIViewController.current()?.hud.showTip(codedec)
                }
                return
            }
        case .ping(_),
                .pong(_),
                .viabilityChanged(_),
                .reconnectSuggested(_):
            break
        case
                .cancelled,
                .peerClosed:
            isConnected = false
            break
        }
    }
    
    private func removefirstType(jsonString: String) -> [String: Any]? {
        var newStr = jsonString.components(separatedBy: ",")
        newStr.removeFirst()
        var dic = [String: Any]()
        for item in newStr {
            var newitem = item
            if !newitem.contains("{") {
                newitem.insert("{", at: newitem.startIndex)
            }
            if !newitem.contains("}") {
                newitem.insert("}", at: newitem.endIndex)
            }
            if let data = newitem.data(using: .utf8) {
                let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
                if let key = json?.keys.first {
                    dic[key] = json?[key]
                }
            }
        }
        return dic
    }
    
    private func headleReceviceTextType(reciveType: WebSocketReceiveType, dic: [String: Any]?) {
        guard let dic = dic else { return  }
        self.delegate?.webSocket(event: reciveType, params: dic)
        switch reciveType {
        case .SetSessionId:
            sessionId = dic["session_id"] as? String ?? ""
            
            rtc.agora_section_id_flg += 1
        case .SetAgoraAppid:
            rtc.agora_appid = dic["agora_appid"] as? String ?? ""
            rtc.agora_appid_flg += 1
        case .SetAgoraToken:
            rtc.agora_token = dic["agora_token"] as? String ?? ""
            rtc.agora_token_flg += 1
        case .SetAgoraChannelId:
            rtc.agora_channel_id = dic["agora_channel_id"] as? String ?? ""
            rtc.agora_channel_id_flg += 1
        case .None:
            break
        case .CreateSessionResult:
            
            let responde_to = dic["responde_to"] as? String ?? ""
            let session_id = dic["session_id"] as? String ?? ""
            sessionId = session_id
            if UserDefaults.standard.bool(forKey: "k_siting") {
                UIPasteboard.general.string = sessionId
            }
            rtc.agora_section_id_flg += 1
            self.request_id = responde_to
            
            // 创建好session 请求info
            RequestAgoraChannelInfo()
            
        case .AgoraChannelInfo:
            
            //            let responde_to = dic["responde_to"] as? String ?? ""
            let appid = dic["appid"] as? String ?? ""
            let channel_id = dic["channel_id"] as? String ?? ""
            //            let server_uid = dic["server_uid"] as? String ?? ""
            rtc.agora_channel_id = channel_id
            rtc.agora_channel_id_flg += 1
            
            rtc.agora_appid = appid
            rtc.agora_appid_flg += 1
            
            rtc.agora_channel_id = channel_id
            
            //获取了info 请求token
            RequestAgoraToken()
            
        case .AgoraToken:
            let client_uid = dic["client_uid"] as? String ?? ""
            //            let duration = dic["duration"] as? String ?? ""
            let token = dic["token"] as? String ?? ""
            rtc.agora_token = token
            rtc.agora_token_flg += 1
            rtc.client_uid = client_uid
            
            // 启用ASR
//            ConfigASR()
            // 启动远端服务
            StartServing()
            break
        }
        if rtc.isfinashConfig {
            connnectCompletion?(true)
            connnectCompletion = nil
            delegate?.webSocket(agoraComplation: rtc, audioOnly: isOnlyAudio)
        }
    }
    private func headleFiveoStatus(reciveType: FiveoSpeakStatus, dic: [String: Any]?) {
        self.delegate?.webSocket(speakstatus: reciveType, params: dic)
    }
    
}
extension FiveoWebSocket {
    // 从归档文件中读取模型对象
    static func loadWebSocketParams(from fileName: String) -> WebSocketParams? {
        let fileManager = FileManager.default
        guard let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            MHLog.log("无法找到文档目录")
            return nil
        }
        
        let filePath = documentsPath.appendingPathComponent(fileName)
        MHLog.log("尝试读取文件路径: \(filePath.path)") // 打印读取路径
        
        do {
            let data = try Data(contentsOf: filePath) // 读取文件数据
            let params = try PropertyListDecoder().decode(WebSocketParams.self, from: data) // 解码数据为模型对象
            return params
        } catch {
            MHLog.log("读取失败: \(error.localizedDescription)")
            return nil
        }
    }
    
    
    // 将模型对象归档存储到沙盒中的文件路径
    static func saveWebSocketParams(_ params: WebSocketParams, to fileName: String) {
        let fileManager = FileManager.default
        guard let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            MHLog.log("无法找到文档目录")
            return
        }
        
        let filePath = documentsPath.appendingPathComponent(fileName)
        
        do {
            let encoder = PropertyListEncoder()
            encoder.outputFormat = .binary // 明确指定二进制格式
            let data = try encoder.encode(params)
            try data.write(to: filePath)
            MHLog.log("保存成功，路径: \(filePath.path)")
        } catch {
            MHLog.log("保存失败: \(error.localizedDescription)")
        }
    }
}

extension FiveoWebSocket {
    
    
    func setDefultSetsyStem_prompt_Voice() {
        var dic = [String: Any]()
        print("setSystem_prompt_defult:\(params.system_prompt_custom_subttile)")
        dic["type"] = "SetSystemPrompt"
        dic["prompt_type"] = "voice"
        dic["system_prompt"] = params.system_prompt_custom_subttile
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
        setEnablePerception()// 链接成功 设置感知模式
        
    }
    
    
    func setEnablePerception() {
        let perception = UserDefaults.standard.bool(forKey: "K_close_perception")
        if perception {
            return
        }
        MHLog.log("setEnablePerception:\(!perception)")
        var dic = [String: Any]()
        dic["type"] = "EnablePerception"
        dic["request_id"] = ""
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    func setsyStem_prompt_Scene(system_prompt: String) {
        
        if system_prompt.isEmpty {
            return
        }
        MHLog.log("setSystem_prompt:\(system_prompt)")
        var dic = [String: Any]()
        dic["type"] = "SetSystemPrompt"
        dic["prompt_type"] = "scene"
        let prompt = system_prompt
        dic["system_prompt"] = prompt
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    private func CreateSession() {
        var dic = [String: Any]()
        dic["type"] = "CreateSession"
        //        if params.system_prompt_custom_isselecated {
        //            dic["system_prompt"] = params.system_prompt_custom_subttile
        //        }else if !params.system_prompt_subttile.isEmpty{
        //            dic["system_prompt"] = params.system_prompt_subttile
        //        }
        dic["request_id"] = RouterService.rs.userInfo.access
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
        
    }
    private func RequestAgoraChannelInfo() {
        var dic = [String: Any]()
        dic["type"] = "RequestAgoraChannelInfo"
        dic["request_id"] = request_id
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    private func RequestAgoraToken() {
        var dic = [String: Any]()
        dic["type"] = "RequestAgoraToken"
        dic["duration"] = 24 * 3600
        dic["request_id"] = request_id
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    /// 启动服务端服务
    private func ConfigASR() {
        var dic = [String: Any]()
        dic["type"] = "ConfigASR"
        dic["enable"] = true
        if let jsonStr = dic.jsonString() {
            MHLog.log(jsonStr)
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    /// 启动服务端服务
    private func StartServing() {
        var dic = [String: Any]()
        MHLog.log("StartServing")
        dic["type"] = "StartServing"
        if let jsonStr = dic.jsonString() {
            MHLog.log(jsonStr)
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    func PostSayIt(text: String) {
        var dic = [String: Any]()
        dic["type"] = "PostSayIt"
        dic["text"] = text
        if let jsonStr = dic.jsonString() {
            socket?.write(string: jsonStr, completion: {
                
            })
        }
    }
    public  func wirteMultimodalGenerate(text: String,
                                         images: [String]?,
                                         audio: [String]?,
                                         completion: ((_ success: Bool) -> ())?) {
        var dic = [String: Any]()
        dic["type"] = "PostMultimodalGenerate"
        if !text.isEmpty {
            dic["text"] = text
        }
        if let images = images, !images.isEmpty {
            var imagesdicArray = [Any]()
            for item in images {
                var imagesdic = [String: Any]()
                imagesdic["type"] = "jpg"
                imagesdic["url"] = item
                imagesdicArray.append(imagesdic)
            }
            dic["images"] = imagesdicArray
        }
        if let audio = audio, !audio.isEmpty {
            var audioDic = [String: Any]()
            audioDic["type"] = "wav"
            audioDic["url"] = audio.first ?? ""
            dic["audio"] = audioDic
        }
        if let jsonStr = dic.jsonString() {
            engine.writeFiveo(string: jsonStr, completion: completion)
        }
    }
    
    
    
    private func codeMessage(code: Int) -> String {
        switch code {
        case -1001: return "当前网络较弱，请检查网络"
        case -1003: return "服务暂时不可用，工程师正在全力修复中"
        case -1004: return "服务暂时不可用，工程师正在全力修复中"
        case -1005: return "网络连接已断开，请检查网络后重试～"
        case -1006: return "服务暂时不可用，工程师正在全力修复中"
        case -1007: return "当前访问量过高，10秒后重试"
        case -1008: return "服务暂时不可用，工程师正在全力修复中"
        case -1009: return "网络连接已断开，请检查网络后重试～"
        case -1010: return "服务暂时不可用，工程师正在全力修复中"
        case -1011: return "服务暂时不可用，工程师正在全力修复中"
        case -1013: return "服务暂时不可用，工程师正在全力修复中"
        case -1014: return "服务暂时不可用，工程师正在全力修复中"
        case -1020: return "网络连接已断开，请检查网络后重试～"
        case -1021: return "网络连接已断开，请检查网络后重试～"
        case -1022: return "服务暂时不可用，工程师正在全力修复中"
        case -1023: return "服务暂时不可用，工程师正在全力修复中"
        default: return ""
        }
    }

}
